/*
Data store object to encapsulate the semantics of specific groundwater data models.
Core VVG library; must load before app.js
Depends on: lib_bore.js

@author: Armin Schmidt, A.Schmidt@GeodataWIZ.com
@version: 1.0
@date: 24 April 2012
@history: V1.0 - 09/04/2012 - Start
*/
(function(vvg){     
	vvg.lib = vvg.lib || {};
	var lib = vvg.lib;  // Shorthand.

    /* 
	==== transect ====
	A 'transect' is a line between two points expressed in a metric coordinate system. 
	The object has the following properties:
		lineString: a two-point linestring geometry running from west to east.
		mathAngle: the angle in radians between the transect and the positive x-axis (as used in calculations like tan() etc.)
		azimuth: the azimuth of the transect in degrees. Clockwise from north, hence 0deg is north, 90deg is east.
		length: the length of the transect between the two points.
	*/
	lib.transect = {};  // Start afresh here.
	lib.transect.pointAngle = function (p1, p2) {
		// Angle from p1 to p2; mathematical in radians, i.e. 0 is positive x-axis, positive is counter-clockwise.
		return Math.atan2(p2.y-p1.y, p2.x-p1.x);
	};
	lib.transect.getTransect = function () {
		// Return a transect object built from the input 
		// which can be one of: two points, a linestring geometry, a feature with a linestring, or a layer
		var fromPoints = function (point1, point2) {
			var swapPoints = function (p1, p2) {
				var p;
				p = p1;
				p1 = p2;
				p2 = p;
			};
			var transect = {};
			if (point1.x > point2.x) {
				swapPoints(point1, point2);
			}
			transect.lineString = new OpenLayers.Geometry.LineString([point1, point2]);
			transect.mathAngle = lib.transect.pointAngle(point1, point2);
			transect.azimuth = 90.0-transect.mathAngle*180.0/Math.PI;
			if (transect.azimuth < 0) {
				transect.azimuth += 360.0;
			}
			transect.length = point1.distanceTo(point2);
			return transect;
		};
		var fromGeometry = function (geometry) {
			if (geometry.components && geometry.components.length >= 2) {
				// Use first and last point.
				return fromPoints(
					geometry.components[0], 
					geometry.components[geometry.components.length-1]
				);
			}
		};
		var fromFeature =  function (feature) {
			return fromGeometry(feature.geometry);
		};
		var fromLayer = function (layer) {
			if (layer.features.length >= 1) {
				return fromFeature(layer.features[0]);  // Take the first feature.
			}
		};

		if (arguments.length === 1) {
			arg = arguments[0];
			switch (arg.CLASS_NAME) {
				case 'OpenLayers.Layer.Vector':
					return fromLayer(arg);
					break;
				case 'OpenLayers.Feature.Vector':
					return fromFeature(arg);
					break;
				case 'OpenLayers.Geometry.LineString':
					return fromGeometry(arg);
					break;
				default:
					return;
			}
		} else if (arguments.length === 2) {
			// Arguments are two points.
			return fromPoints(arguments[0], arguments[1]);
		}
	};

	/*
	==== dataStore ====
	The 'dataStore' class encapsulates all the information known about a dataStore,
	which is composed of knowledge of the database schema, what it means and how it is
	interpreted as a boreTransect.
	*/
	lib.dataStore = function (dataStoreDescriptor) {
		/*
		dataStoreDescriptor has the following properties:
			parametersWMS
				name
				url
				params
			parametersWFS
				name
				params  // includes .url
			featureToBore: function (feature) {
				...
				return bore;
			}
		*/
		var that = {};  // The main data object to hold all public properties and methods.
		
		// Private properties.
		var responseWFS,  // Holds the features returned from the WFS.
			loadWFS_State = {
				// This is passed to the callback functions for the loadWFS waiting loop.
				// Made a class variable so that it is accessible to other 
				// class methods, e.g. stopLoading().
				durationMilliSec: 0,  // How long have we been waiting for the load to complete?
				aborted: false,
				timedout: false,
				completed: false
			};
		
		// Private methods.
		var init = function (dataStoreDescriptor) {
			// 'Constructor'. May be empty.
		};
		
		that.layerWMS = function () {
			// Return the data as a WMS layer.
			if (dataStoreDescriptor.parametersWMS.url) {
				return new OpenLayers.Layer.WMS(
					dataStoreDescriptor.parametersWMS.name,
					dataStoreDescriptor.parametersWMS.url,
					dataStoreDescriptor.parametersWMS.params
				);
			} else {
				return;
			}
		};
		that.layerWFS = function (transect, distance, callback) {
			// Read the bore data from the WFS. Interact with the loading process through callback functions.
			// transect = {linestring, mathAngle, azimuth, length}; see above
			// distance = {value, units = 'm', otherwise assumed 'degrees'}
			// callback = {funcStart, funcWhileWait, funcStop, timeoutMilliSec}
			
			var transectFilter = function (transect, distance) {
				// Define the buffer filter for the transect, 
				// either with DWITHIN, or by constructing a buffer polygon, or by using a BBOX.
				var bestAvailableFilter = OpenLayers.Filter.Spatial.DWITHIN;
				var metresPerDegree = 112500.0;  // Approximate number of metres per longitude degree in Victoria.
				var olFilter;
				var lineString;
				
				var projections = {
					map:  {srsName: 'EPSG:900913'},	// Map is in spherical mercator = Google.
					display: {srsName: 'EPSG:4326'}		// Display is in WGS84 lat/lon.
				};
				var projection;
				for (projection in projections) {
					projections[projection].olProjection = new OpenLayers.Projection(projections[projection].srsName);
				}
				
				lineString = transect.lineString.clone();
				//lineString.transform(projections.display.olProjection, projections.map.olProjection);
				
				switch (bestAvailableFilter) {
					case OpenLayers.Filter.Spatial.DWITHIN:
						olFilter = new OpenLayers.Filter.Spatial({
							type: OpenLayers.Filter.Spatial.DWITHIN,
							distanceUnits: 'm',
							distance: distance.value/metresPerDegree,
							//distance: 0.05,  // This seems to be in units of the underlying data, regardless of any applied transformation, or distanceUnits.
							//distance: 1000,
							projection: projections.map.srsName,
							//projection: projections.display.srsName,
							value: lineString
						});
						break;
					default:
						olFilter = {};
						break;
				}
				return olFilter;
			};
			var setTimeoutAS = function (func, delay) {
				// Selfmade delay function.
				var start = +new Date();
				while (+new Date() - start < delay) {
					// Do something slow.
					Math.sin(Math.random());
				}
				func();
			};
			
			var callbackDescriptor = {
				// Contains all callback information for the WFS load operation.
				timeStart: 0,
				waitStepMilliSec: 1000,  // Delay in (msec) between calling funcWhileWait.
				timeoutMilliSec: 30000,  // Default value, to be overwritten.
				funcStart: function (state) {
					var a = 1;  // For debugging.
				},
				funcWhileWait: function () {
					var b = 2;  // For debugging.
				},
				funcStop: function (state) {
					var c = 3;  // For debugging.
				},
				funcCompleted: function () {
					loadWFS_State.completed = true;
				}
			};
			
			if (callback) {
				// Use the callback information passed in as parameter.
				callbackDescriptor.funcStart = callback.funcStart || callbackDescriptor.funcStart;
				if (callback.funcWhileWait) {
					// Make sure the external callback.funcWhileWait() has access to loadWFS_State.
					callbackDescriptor.funcWhileWait = function () {
						callback.funcWhileWait(loadWFS_State);
					};
				}
				callbackDescriptor.funcStop = callback.funcStop || callbackDescriptor.funcStop;
				callbackDescriptor.timeoutMilliSec = 
					callback.timeoutMilliSec || callbackDescriptor.timeoutMilliSec;  // Deals with undefined and 0.
			}

			var layer = new OpenLayers.Layer.Vector(dataStoreDescriptor.parametersWFS.name);
			var filter = transectFilter(transect, distance);
			dataStoreDescriptor.parametersWFS.params.defaultFilter = dataStoreDescriptor.parametersWFS.params.defaultFilter || filter;
			var protocol = new OpenLayers.Protocol.WFS(dataStoreDescriptor.parametersWFS.params);
			
			loadWFS_State = {
				durationMilliSec: 0,
				aborted: false,
				timedout: false,
				completed: false
			};
			callbackDescriptor.timeStart = +new Date();
			responseWFS = protocol.read({callback: callbackDescriptor.funcCompleted});  // Start the WFS query.
			callbackDescriptor.funcStart(loadWFS_State);

			// Loop to wait for the WFS query to complete.
			while (
				(loadWFS_State.durationMilliSec < callbackDescriptor.timeoutMilliSec)
				&& !loadWFS_State.aborted 
				&& !loadWFS_State.completed
			) {
				loadWFS_State.durationMilliSec = +new Date() - callbackDescriptor.timeStart;
				// The real setTimeout call seems to crash Firefox. Maybe one cannot call a local function, only global ones?
				setTimeoutAS(
					callbackDescriptor.funcWhileWait, // the loadWFS_State is internally passed to the externally defined function.
					callbackDescriptor.waitStepMilliSec
				);
			}
			callbackDescriptor.funcStop(loadWFS_State);
			
			if (loadWFS_State.completed) {
				// May have to also check for responseWFS.error.
				layer.addFeatures(responseWFS.features);
			}
			return layer;
		};
		that.test = function () {
			var layer = new OpenLayers.Layer.Vector(dataStoreDescriptor.parametersWFS.name);
			alert(responseWFS.features.length);
			layer.addFeatures(responseWFS.features);
			return layer;
		};
		that.boreTransect = function () {
			// Return the boreTransect corresponding to the recently queried WFS data.
			// @@
		};
		that.stopLoading = function () {
			// Stop the loading process.
			loadWFS_State.aborted = true;
		};
		
		init(dataStoreDescriptor);
		return that;
	};
	
	lib.dataStoreAqWl = lib.dataStore({
		// Bores with acquifers and waterlevels.
		parametersWMS: {
			name: 'DPI-Vic bores WMS',
			url: 'http://siss.versi.edu.au/geoserver/wms?service=WMS',
			params: {
				layers: 'mv_vvg_bores_aq_wl', 
				transparent: true,
				isBaseLayer: false,
				singleTile: true,
				opacity: 0.6,
				visibility: true
			}
		},
		parametersWFS: {
			name: 'DPI-Vic bores WFS',
			params: {
				url: 'http://siss.versi.edu.au/geoserver/wfs?service=WFS',
				featureType: 'mv_vvg_bores_aq_wl',
				geometryName: 'geom_loc',
				extractAttributes : true,
				//maxFeatures: 1000,
				srsName: 'EPSG:900913',  // SRS of the map.
				version: '1.1.0'
			}
		},
		featureToBore: function (feature) {
			var bore = {};
			return bore;
		}
	});
}(
window.vvg = window.vvg || {}
));
